<?php


namespace UU\Suite\Kernel\Traits;


trait InteractWithHandlers
{
    protected array $handlers = [];

    public function getHandlers(): array
    {
        return $this->handlers;
    }

    /**
     * @param callable|string $handler
     * @return $this
     */
    public function with($handler): self
    {
        return $this->withHandler(...\func_get_args());
    }

    /**
     * @param callable|string $handler
     * @return $this
     */
    public function without($handler): self
    {
        return $this->withoutHandler(...\func_get_args());
    }

    /**
     * @param $value
     * @param callable|string $handler
     * @return $this
     */
    public function when($value, $handler): self
    {
        if (\is_callable($value)) {
            $value = \call_user_func($value, $this);
        }

        !!$value && $this->withHandler($handler);

        return $this;
    }

    /**
     * @param $value
     * @param callable|string $handler
     * @return $this
     */
    public function unless($value, $handler): self
    {
        if (\is_callable($value)) {
            $value = \call_user_func($value, $this);
        }

        !$value && $this->withHandler($handler);

        return $this;
    }

    public function handle($result, $payload = null)
    {
        $next = \is_callable($result) ? $result : fn($p) => $result;

        foreach ($this->handlers as $handler) {
            $next = fn( $p)  => $handler($p, $next);
        }

        return $next($payload);
    }

    /**
     * @param callable|string $handler
     * @return bool
     */
    public function has($handler): bool
    {
        return \array_key_exists($this->getHandlerHash($handler), $this->handlers);
    }

    /**
     * @param callable|string $handler
     * @return $this
     */
    public function withHandler( $handler): self
    {
        $handler = $this->makeClosure($handler);

        $this->handlers[$this->getHandlerHash($handler)] = $handler;
        return $this;
    }

    /**
     * @param array $handlers
     * @return $this
     */
    public function withHandlers(array $handlers): self
    {
        foreach ($handlers as $handler) {
            $this->withHandler($handler);
        }

        return $this;
    }

    /**
     * @param callable|string $handler
     * @return $this
     */
    public function withoutHandler( $handler): self
    {
        $handler = $this->makeClosure($handler);

        unset($this->handlers[$this->getHandlerHash($handler)]);

        return $this;
    }

    /**
     * @param array|null $handlers
     * @return $this
     */
    public function withoutHandlers(array $handlers = null): self
    {
        if (!is_array($handlers)) {
            $handlers = $this->handlers;
        }

        foreach ($handlers as $handler) {
            $this->withoutHandler($handler);
        }

        return $this;
    }

    /**
     * @param callable|string $handler
     * @return string
     */
    protected function getHandlerHash($handler): string
    {
        switch (true) {
            case \is_string($handler) :
                return $handler;

            case \is_array($handler):
                return is_string($handler[0]) ? $handler[0] . '::' . $handler[1] : get_class($handler[0]) . $handler[1];
            case $handler instanceof \Closure:
                return \spl_object_hash($handler);

        }
    }

    /**
     * @param callable|string $handler
     * @return callable|string
     */
    protected function makeClosure($handler)
    {
        if (is_callable($handler)) {
            return $handler;
        }

        if (class_exists($handler) && \method_exists($handler, '__invoke')) {
            return $handler;
        }

        throw new \InvalidArgumentException(sprintf('Invalid handler: %s.', $handler));
    }
}